#include "my_header.h"
#define BIGFILE 100000000

// 用来传输数据的结构
typedef struct train_s
{
	int len;
	char data[1024];
} train_t;

// 上传指定的文件到网盘，是从client中上传一个文件到用户空间
int upload_file(int socket_fd, char *path)
{
	train_t train;
	bzero(&train, sizeof(train));

	// 打开要上传的文件，获取其文件描述符
	int file_fd = open(path, O_RDWR);
	ERROR_CHECK(file_fd, -1, "open");
	// 获取文件的大小，发送给服务器
	struct stat st;
	fstat(file_fd, &st);
	send(socket_fd, &st.st_size, sizeof(st.st_size), MSG_NOSIGNAL);

	if (st.st_size >= BIGFILE) { // 大文件
		sendfile(socket_fd, file_fd, NULL, st.st_size);
	} else { // 小文件
		while (1) {
			ssize_t res_read = read(file_fd, &train.data, sizeof(train.data));
			if (res_read == 0) {
				break;
			}
			train.len = res_read;
			send(socket_fd, &train, sizeof(train.len) + train.len, MSG_NOSIGNAL);
		}
	}
	// 通知服务端已发送完毕
	train.len = 0;
	send(socket_fd, &train, sizeof(train.len) + train.len, MSG_NOSIGNAL);

	printf("文件大小：%ld\n", st.st_size);
	printf("文件上传完毕！\n");

	close(file_fd);
	return 0;
}

// 下载指定的网盘文件到本地，从用户空间到client目录，gets ../hello.txt
int download_file(int socket_fd, char *path) {
	train_t train;
	bzero(&train, sizeof(train));

	// 存储文件名字，需要从path中截取
	char file_name[1024];
	int i;
	for (i = strlen(path) - 1; i > 0; i--) {
		if (path[i] == '/') {
			// 路径从后往前第一个/
			break;
		}
	}
	if (i) {
		strcpy(file_name, (path + i + 1));
	} else { // i==0,意味path存的就是个文件名
		strcpy(file_name, path);
	}
	printf("file_name: %s\n", file_name);

	// 先检查./client目录下是否存在要下载的文件
	int ret_acs = access(file_name, F_OK);
	int file_fd;
	// 这个已存在文件的大小
	off_t cur_size = 0;
	if(ret_acs == 0) {
		// 已经存在，要么已经下载完成，要么下载时被截断了
		file_fd = open(file_name, O_RDWR|O_APPEND);
		ERROR_CHECK(file_fd, -1, "open");
		struct stat cur_st;
		fstat(file_fd, &cur_st);
		cur_size = cur_st.st_size;
		train.len = sizeof(cur_size);
		memcpy(train.data, &cur_size, train.len);
		// 告诉服务端目前存在的这个文件的大小
		send(socket_fd, &train, sizeof(train.len) + train.len, MSG_NOSIGNAL);
		lseek(file_fd, cur_size, SEEK_SET);
	} else {
		// 创建文件到./client目录下
		file_fd = open(file_name, O_RDWR | O_CREAT | O_EXCL, 0666);
		ERROR_CHECK(file_fd, -1, "open");
		train.len = sizeof(cur_size);
		memcpy(train.data, &cur_size, train.len);
		send(socket_fd, &train, sizeof(train.len) + train.len, MSG_NOSIGNAL);
	}
	
	// 接收服务端即将发送过来的文件的大小（剩余文件的大小）
	off_t file_size = 0;
	bzero(&train, sizeof(train));
	recv(socket_fd, &file_size, sizeof(off_t), MSG_WAITALL);
	printf("文件大小：%ld\n", file_size);

	if (file_size >= BIGFILE) {
		ftruncate(file_fd, file_size + cur_size);
		char *p = (char *)mmap(NULL, file_size, PROT_READ | PROT_WRITE, MAP_SHARED, file_fd, 0);
		ERROR_CHECK(p, MAP_FAILED, "mmap");
		recv(socket_fd, p, file_size, MSG_WAITALL);
		munmap(p, file_size);
	} else {
		while (1) {
			recv(socket_fd, &train.len, sizeof(train.len), MSG_WAITALL);
			if (train.len == 0) {
				break;
			}
			recv(socket_fd, train.data, train.len, MSG_WAITALL);
			write(file_fd, train.data, train.len);
		}
	}
	printf("100.00%%\n");
	printf("文件下载完成！\n");
	close(file_fd);
	return 0;
}

// 执行用户输入的指令
int execute_command(char *buf, int socket_fd)
{
	train_t train;
	bzero(&train, sizeof(train));
	int buf_len = strlen(buf);
	buf[buf_len - 1] = '\0'; // ---------------------> buf_len - 1
	// 获取指令的开头字段，比如从"cd /test"中获取到"cd"
	char *cmd = strtok(buf, " ");

	if (!strcmp(cmd, "cd"))
	{ // 切换目录
		// 发送cmd：cd
		bzero(&train, sizeof(train));
		train.len = strlen(cmd);
		memcpy(train.data, cmd, train.len);
		send(socket_fd, &train, sizeof(train.len) + train.len, MSG_NOSIGNAL);
		// 发送要cd的路径path
		bzero(&train, sizeof(train));
		char *path = strtok(NULL, " ");
		train.len = strlen(path);
		memcpy(train.data, path, train.len);
		send(socket_fd, &train, sizeof(train.len) + train.len, MSG_NOSIGNAL);
		// 是否cd成功
		bzero(&train, sizeof(train));
		recv(socket_fd, &train.len, sizeof(train.len), MSG_WAITALL);
		recv(socket_fd, train.data, train.len, MSG_WAITALL);
		int cmp = strcmp(train.data, "success");
		if(cmp == 0) {
			printf("目录切换成功！\n");
		} else {
			printf("目录切换失败！您输入的路径不存在！\n");
		}
		
	}
	else if (!strcmp(cmd, "pwd"))
	{ // 显示当前目录的路径
		bzero(&train, sizeof(train));
		train.len = strlen(cmd);
		memcpy(train.data, cmd, train.len);
		send(socket_fd, &train, sizeof(train.len) + train.len, MSG_NOSIGNAL);

		bzero(&train, sizeof(train));
		recv(socket_fd, &train.len, sizeof(train.len), MSG_WAITALL);
		recv(socket_fd, train.data, train.len, MSG_WAITALL);

		// 打印当前目录的路径
		puts(train.data);
	}
	else if (!strcmp(cmd, "ls"))
	{ // 显示当前目录下所有文件的名字
		bzero(&train, sizeof(train));
		train.len = strlen(cmd);
		memcpy(train.data, cmd, train.len);
		send(socket_fd, &train, sizeof(train.len) + train.len, MSG_NOSIGNAL);

		bzero(&train, sizeof(train));
		recv(socket_fd, &train.len, sizeof(train.len), MSG_WAITALL);
		recv(socket_fd, train.data, train.len, MSG_WAITALL);

		// 打印当前目录下的所有文件名
		char *p;
		for (p = strtok(train.data, "/"); p != NULL; p = strtok(NULL, "/"))
		{
			printf("%s  ", p);
		}
		printf("\n");
	}
	else if (!strcmp(cmd, "puts"))
	{ // 上传本地文件到网盘
		// 发送cmd：puts
		bzero(&train, sizeof(train));
		train.len = strlen(cmd);
		memcpy(train.data, cmd, train.len);
		send(socket_fd, &train, sizeof(train.len) + train.len, MSG_NOSIGNAL);
		// 要上传的文件path
		bzero(&train, sizeof(train));
		char *path = strtok(NULL, " ");
		train.len = strlen(path);
		memcpy(train.data, path, train.len);
		// 发送文件名给服务端
		send(socket_fd, &train, sizeof(train.len) + train.len, MSG_NOSIGNAL);
		// 上传文件
		upload_file(socket_fd, path);
		/* recv(socket_fd, &train.len, sizeof(train.len), MSG_WAITALL);
		if(train.len == 0) {
			printf("puts error\n");
			return -1;
		}
		else {
			// 上传文件
			upload_file(socket_fd, path);
		} */
	}
	else if (!strcmp(cmd, "gets"))
	{ // 下载指定的网盘文件到本地
		// 发送cmd：gets
		bzero(&train, sizeof(train));
		train.len = strlen(cmd);
		memcpy(train.data, cmd, train.len);
		send(socket_fd, &train, sizeof(train.len) + train.len, MSG_NOSIGNAL);
		// 要下载的文件path
		bzero(&train, sizeof(train));
		char *path = strtok(NULL, " ");
		train.len = strlen(path);
		memcpy(train.data, path, train.len);
		send(socket_fd, &train, sizeof(train.len) + train.len, MSG_NOSIGNAL);
		// 下载文件
		download_file(socket_fd, path);
		/* recv(socket_fd, &train.len, sizeof(train.len), MSG_WAITALL);
		if(train.len == 0) {
			printf("gets error\n");
			return -1;
		}
		else {
			// 下载文件
			download_file(socket_fd, path);
		} */
	}
	else if (!strcmp(cmd, "remove"))
	{ // 删除指定的网盘文件
		// 发送cmd：remove
		bzero(&train, sizeof(train));
		train.len = strlen(cmd);
		memcpy(train.data, cmd, train.len);
		send(socket_fd, &train, sizeof(train.len) + train.len, MSG_NOSIGNAL);
		// 发送要remove的文件的路径path
		bzero(&train, sizeof(train));
		char *path = strtok(NULL, " ");
		train.len = strlen(path);
		memcpy(train.data, path, train.len);
		send(socket_fd, &train, sizeof(train.len) + train.len, MSG_NOSIGNAL);

		printf("成功删除文件！\n");
	}
	else
	{ // 错误指令
		printf("输入了错误的指令！\n");
		return -1;
	}
	return 0;
}

// 客户端的主函数
int main(int argc, char *argv[])
{
	// 用户需要指定所连接的服务器的ip地址和端口号
	// 可执行程序 ip 端口
	ARGS_CHECK(argc, 3);
	char *ip = argv[1];
	char *port = argv[2];

	// 建立socket连接
	int socket_fd = socket(AF_INET, SOCK_STREAM, 0);
	ERROR_CHECK(socket_fd, -1, "socket");
	struct sockaddr_in sockaddr;
	sockaddr.sin_family = AF_INET;
	sockaddr.sin_addr.s_addr = inet_addr(ip);
	sockaddr.sin_port = htons(atoi(port));
	int res_conn = connect(socket_fd, (struct sockaddr *)&sockaddr, sizeof(sockaddr));
	ERROR_CHECK(res_conn, -1, "connect");

	train_t train;
	bzero(&train, sizeof(train));

	// 服务端需要验证用户身份
	char username[1024] = {0};
	char password[1024] = {0};
	printf("在使用网盘服务前需要验证您的身份：\n");
	printf("请输入用户名：\n");
	scanf("%s", username);
	train.len = strlen(username);
	memcpy(train.data, username, train.len);
	send(socket_fd, &train, sizeof(train.len) + train.len, MSG_NOSIGNAL);

	bzero(&train, sizeof(train));
	printf("请输入密码：\n");
	scanf("%s", password);
	train.len = strlen(password);
	memcpy(train.data, password, train.len);
	send(socket_fd, &train, sizeof(train.len) + train.len, MSG_NOSIGNAL);

	char login[1024] = {0};
	while (1)
	{
		bzero(&login, sizeof(login));
		recv(socket_fd, &train.len, sizeof(train.len), MSG_WAITALL);
		recv(socket_fd, login, train.len, MSG_WAITALL);
		int cmp = strcmp(login, "success");
		if (cmp == 0)
		{
			printf("密码正确！\n");
			printf("登录成功！\n");
			// 打印指令菜单
			printf("**********菜单**********\n");
			printf("1.cd:进入网盘对应目录\n"
						 "2.ls:列出网盘相应目录文件\n"
						 "3.puts:上传本地文件到网盘\n"
						 "4.gets:下载网盘文件到本地\n"
						 "5.remove:删除网盘文件\n"
						 "6.pwd:显示网盘当前路径\n"
						 "7.ctrl+d:退出网盘\n");
			break;
		}
		else
		{
			printf("密码错误！\n");
			bzero(password, sizeof(password));
			printf("请重新输入密码：\n");
			int rets = scanf("%s", password);
			if (rets == EOF)
			{
				train.len = 0;
				send(socket_fd, &train, sizeof(train.len) + train.len, MSG_NOSIGNAL);
				close(socket_fd);
				return 0;
			}
			train.len = strlen(password);
			memcpy(train.data, password, train.len);
			send(socket_fd, &train, sizeof(train.len) + train.len, MSG_NOSIGNAL);
		}
	}

	printf("可以输入指令了\n");
	// 已成功连接上服务器，可以输入菜单指令
	char buf[4096] = {0};
	// 监听集合
	fd_set set;
	while (1)
	{
		FD_ZERO(&set);
		FD_SET(socket_fd, &set);
		FD_SET(STDIN_FILENO, &set);
		int res_sel = select(1024, &set, NULL, NULL, NULL);
		ERROR_CHECK(res_sel, -1, "select");

		// 监听到用户输入
		if (FD_ISSET(STDIN_FILENO, &set))
		{
			// 读取用户输入
			ssize_t res_read1 = read(STDIN_FILENO, buf, sizeof(buf));
			ERROR_CHECK(res_read1, -1, "read");
			// 用户通过Ctrl+d退出时
			if (res_read1 == 0)
			{
				bzero(&train, sizeof(train));
				train.len = 0;
				send(socket_fd, &train, sizeof(train.len) + train.len, MSG_NOSIGNAL);
				printf("用户主动退出！\n");
				break;
			}
			// 执行用户输入得到指令
			execute_command(buf, socket_fd);
			bzero(buf, sizeof(buf));
		}

		// 监听到服务端消息
		if (FD_ISSET(socket_fd, &set))
		{
			ssize_t res_read2 = read(socket_fd, buf, sizeof(buf));
			ERROR_CHECK(res_read2, -1, "read");
			if (res_read2 == 0)
			{
				printf("服务端断开连接！\n");
				break;
			}
		}
	}
	// 关闭socket连接
	close(socket_fd);
	return 0;
}